#ifndef __K2L_H__
#define __K2L_H__

#define K2L_ABORT(...) do { printf(__VA_ARGS__); abort(); } while(0)
#define K2L_ABORT_S(s, ...) do { printf(__VA_ARGS__); k2l_disp_line(s, K2L_STDOUT); abort(); } while(0)

typedef char           k2l_char;
typedef int            k2l_int;
typedef unsigned char  k2l_u8;
typedef unsigned int   k2l_u32;

typedef enum {
	SREF_TYPE_SOBJ=0, SREF_TYPE_INT, SREF_TYPE_CHR, SREF_TYPE_UNDEF,
} k2l_sexp_ref_type_e;

typedef struct {
	k2l_u32 refcnt;
	const struct _k2l_type_t *type;
} k2l_sobj_t;

typedef union {
	k2l_sobj_t *ptr;
	struct {
		k2l_sexp_ref_type_e type: 2;
		k2l_int imm: 30;
	} t;
	k2l_u32 w;
} k2l_sexp_ref;

typedef struct {
	void (*release)(k2l_sexp_ref);
	void (*print)(k2l_sexp_ref);
} k2l_sobj_ops;

typedef struct _k2l_type_t {
	const struct _k2l_type_t *parent;
	k2l_sexp_ref specifier;
	k2l_sobj_ops *ops;
} k2l_type_t;

const k2l_type_t k2l_ty_sequence, k2l_ty_string, k2l_ty_cons, k2l_ty_symbol, k2l_ty_stream, k2l_ty_hashtab, 
	k2l_ty_function, k2l_ty_lambdafn, k2l_ty_specialop, k2l_ty_macro, k2l_ty_env, k2l_ty_syspriv;

#define SOBJ_REF(u) (((k2l_sobj_t*)(u).ptr)->refcnt)
#define SOBJ_TYPE(u) (((k2l_sobj_t*)(u).ptr)->type)
#define SOBJ_OPS(u) (SOBJ_TYPE(u)->ops)
#define imm_int(u) ((k2l_int)((u).t.imm))
#define imm_char(u) ((k2l_char)((u).t.imm))
#define DECLARE_REF(a) {(k2l_sobj_t*)a}

typedef const struct {
	k2l_sobj_ops suqer;
	k2l_int (*length)(k2l_sexp_ref);
	k2l_sexp_ref (*elt)(k2l_sexp_ref, k2l_int);
	void (*replace)(k2l_sexp_ref seq1, k2l_sexp_ref seq2, k2l_int pos1, k2l_int pos2, k2l_int len);
} k2l_sequence_ops;
#define SEQ_LENGTH(u)  (((k2l_sequence_ops*)SOBJ_OPS(u))->length(u))
#define SEQ_ELT(u,i) (((k2l_sequence_ops*)SOBJ_OPS(u))->elt(u, i))
#define SEQ_REPLACE(s1,s2,p1,p2,len) (((k2l_sequence_ops*)SOBJ_OPS(s1))->replace(s1,s2,p1,p2,len))

typedef struct {
	k2l_sobj_t sexp;
	k2l_sexp_ref car;
	k2l_sexp_ref cdr;
} k2l_cons_t;
#define CONS_CAR(u) (((k2l_cons_t*)(u).ptr)->car)
#define CONS_CDR(u) (((k2l_cons_t*)(u).ptr)->cdr)
#define CONS_FIRST(u) (CONS_CAR(u))
#define CONS_SECOND(u) (CONS_CAR(CONS_CDR(u)))
#define CONS_THIRD(u) (CONS_CAR(CONS_CDR(CONS_CDR(u))))
#define CONS_FORTH(u) (CONS_CAR(CONS_CDR(CONS_CDR(CONS_CDR(u)))))
#define CONS_FIFTH(u) (CONS_CAR(CONS_CDR(CONS_CDR(CONS_CDR(CONS_CDR(u))))))

typedef struct {
	k2l_sobj_t sexp;
	k2l_int len;
	k2l_char *head;
} k2l_string_t;
#define STRING_HEAD(u) (((k2l_string_t*)(u).ptr)->head)
#define STRING_LEN(u) (((k2l_string_t*)(u).ptr)->len)
#define DECLARE_STRING(str) {{0, &k2l_ty_string}, strlen(str), str}

typedef struct {
	k2l_sobj_t sexp;
	k2l_u32 flags;
	k2l_sexp_ref name; // k2l_string_t
	k2l_sexp_ref val;
	k2l_sexp_ref fnc; // function_t
} k2l_symbol_t;
#define SYMBOL_FLG_KEYWORD 1
#define SYMBOL_FLG_CONSTANT 2
#define SYMBOL_FLAGS(p) (((k2l_symbol_t*)(p).ptr)->flags)
#define SYMBOL_NAME(p) (((k2l_symbol_t*)(p).ptr)->name)
#define SYMBOL_VALUE(p) (((k2l_symbol_t*)(p).ptr)->val)
#define SYMBOL_FUNCTION(p) (((k2l_symbol_t*)(p).ptr)->fnc)
#define DECLARE_SYMBOL(name,v,f) {{0, &k2l_ty_symbol}, 0, DECLARE_REF(&(k2l_string_t)DECLARE_STRING(name)), v, f}

typedef struct {
	k2l_sobj_t sexp;
	k2l_sexp_ref (*fnp)(k2l_sexp_ref, k2l_sexp_ref *mvp);
} k2l_function_t;
#define FUNCTION(u) (((k2l_function_t*)(u).ptr)->fnp)
#define INVOKE_FUNCTION(fn, args, mvp) ((FUNCTION(fn))(args, mvp))
#define DECLARE_FUNCTION(fnp) {{0, &k2l_ty_function}, (fnp)}
#define DECLARE_SYMBOL_FUNCTION(name, fn) DECLARE_SYMBOL(name, K2L_NIL, DECLARE_REF(&(k2l_function_t)DECLARE_FUNCTION(fn)))

typedef struct {
	k2l_sobj_t sexp;
	k2l_sexp_ref (*fnp)(k2l_sexp_ref dyn, k2l_sexp_ref args, k2l_sexp_ref *mvp);
} k2l_specialop_t;
#define SPECIALOP(u) (((k2l_specialop_t*)(u).ptr)->fnp)
#define DECLARE_SPECIALOP(fn) {{0, &k2l_ty_specialop}, (fn)}
#define DECLARE_SYMBOL_SPECIALOP(name, fn) DECLARE_SYMBOL(name, K2L_NIL, DECLARE_REF(&(k2l_specialop_t)DECLARE_SPECIALOP(fn)))
#define INVOKE_SPECIALOP(fn, dyn, args, mvp) ((SPECIALOP(fn))(dyn, args, mvp))

typedef struct {
	k2l_sobj_t sexp;
	k2l_sexp_ref lex; // env_t
	k2l_sexp_ref name; // k2l_string_t 
	k2l_sexp_ref def; // (ARG_DEF . BODY_DEF)
} k2l_lambdafn_t;
#define LAMBDA_LEX(u) (((k2l_lambdafn_t*)(u).ptr)->lex)
#define LAMBDA_NAME(u) (((k2l_lambdafn_t*)(u).ptr)->name)
#define LAMBDA_DEF(u) (((k2l_lambdafn_t*)(u).ptr)->def)
#define LAMBDA_ARG(u) (CONS_CAR(LAMBDA_DEF(u)))
#define LAMBDA_BODY(u) (CONS_CDR(LAMBDA_DEF(u)))
extern k2l_sexp_ref invoke_lambdafn(k2l_sexp_ref fn, k2l_sexp_ref args, k2l_sexp_ref *mvp);
#define INVOKE_LAMBDAFN(fn, args, mvp) (invoke_lambdafn(fn, args, mvp))
#define EXPAND_MACRO(fn, args) INVOKE_LAMBDAFN(fn, args, NULL)

#define INVOKE(fn, dyn, args, mvp) (is_specialop(fn)? INVOKE_SPECIALOP((fn), (dyn), (args), (mvp)): \
									 is_function(fn)? INVOKE_FUNCTION((fn), (args), (mvp)): \
									 is_lambdafn(fn)? INVOKE_LAMBDAFN((fn), (args), (mvp)): g_k2l_nil)

typedef struct {
	k2l_sobj_t sexp;
	k2l_u32 flags;
} k2l_stream_t;
#define STREAM_FLAGS(u) (((k2l_stream_t*)(u).ptr)->flags)

typedef const struct {
	k2l_sobj_ops suqer;
	k2l_char (*getchar)(k2l_sexp_ref);
	void (*reset)(k2l_sexp_ref);
	void (*putchar)(k2l_sexp_ref, k2l_char);
} k2l_stream_ops;
#define STREAM_OPS(u) ((k2l_stream_ops*)((k2l_stream_t*)(u).ptr)->sexp.ops)
#define STREAM_GETCHAR(u) (STREAM_OPS(u)->getchar(u))
#define STREAM_RESET(u) (STREAM_OPS(u)->reset(u))
#define STREAM_PUTCHAR(u, c) (STREAM_OPS(u)->putchar((u), (c)))

typedef struct {
	k2l_stream_t suqer;
	k2l_int fd;
	k2l_int mark;
	k2l_char buf;
} k2l_filedesc_t;
#define FIS_FD(u) (((k2l_filedesc_t*)(u).ptr)->fd)
#define FIS_MARK(u) (((k2l_filedesc_t*)(u).ptr)->mark)
#define FIS_BUF(u) (((k2l_filedesc_t*)(u).ptr)->buf)

typedef struct {
	k2l_stream_t suqer;
	k2l_sexp_ref src; // k2l_string_t*
	k2l_int pos;
} k2l_string_stream_t;
#define STGSTM_SRC(u) (((k2l_string_stream_t*)(u).ptr)->src)
#define STGSTM_POS(u) (((k2l_string_stream_t*)(u).ptr)->pos)

typedef struct {
	k2l_sobj_t sexp;
	k2l_sexp_ref testf; // k2l_function_t
	k2l_sexp_ref tab; // k2l_cons_t
} k2l_hashtab_t;
#define HASHTAB_DIM(u) (((k2l_hashtab_t*)(u).ptr)->dim)
#define HASHTAB_TESTF(u) (((k2l_hashtab_t*)(u).ptr)->testf)
#define HASHTAB_TAB(u) (((k2l_hashtab_t*)(u).ptr)->tab)

typedef struct {
	k2l_sobj_t sexp;
	k2l_sexp_ref parent; // k2l_env_t
	k2l_sexp_ref symtab; // k2l_hashtab_t
} k2l_env_t;
#define ENV_PARENT(u) (((k2l_env_t*)(u).ptr)->parent)
#define ENV_SYMTAB(u) (((k2l_env_t*)(u).ptr)->symtab)

typedef enum {
	K2L_SYS_TYPE_LONGJMP=1, K2L_SYS_TYPE_READ_END_OF_LIST, K2L_SYS_TYPE_READ_END_OF_FILE, K2L_SYS_TYPE_READ_CONS_DOT,
	K2L_SYS_TYPE_READ_COMMA, K2L_SYS_TYPE_READ_COMMA_AT, K2L_SYS_TYPE_LMD_REST, K2L_SYS_TYPE_LMD_KEY, K2L_SYS_TYPE_LMD_OPTIONAL,
} k2l_sys_type_e;

typedef struct {
	k2l_sobj_t sexp;
	k2l_sys_type_e type;
	k2l_sexp_ref privp;
} k2l_syspriv_t;

#define SYS_TYPE(u) (((k2l_syspriv_t*)(u).ptr)->type)
#define SYS_PRIVP(u) (((k2l_syspriv_t*)(u).ptr)->privp)
#define DECLARE_SYSPRIV(n) {{0, &k2l_ty_syspriv}, n, 0}
#define SYSPRIVF_DEF(n) DECLARE_REF(&(k2l_syspriv_t)DECLARE_SYSPRIV(n))

#define is_imm(p) ((p).t.type != 0 || (p).w == 0)
#define is_imm_char(p) ((p).t.type == SREF_TYPE_CHR)
#define is_imm_int(p) ((p).t.type == SREF_TYPE_INT)
#define is_undef(p) ((p).t.type == SREF_TYPE_UNDEF)
#define is_nil(p) ((p).w == 0)
#define is_t(p) (!is_imm(p) && SOBJ_TYPE(p) == &k2l_ty_t)
#define _is_ref_type(ty, p) (!is_imm(p) && SOBJ_TYPE(p) == &(ty))
#define is_cons(p) _is_ref_type(k2l_ty_cons, p)
#define is_symbol(p) _is_ref_type(k2l_ty_symbol, p)
#define is_nil_or_t_or_symbol(p) (is_nil(p) || is_t(p) || is_symbol(p))
#define is_keyword(p) (is_symbol(p) && (SYMBOL_FLAGS(p) == SYMBOL_FLG_KEYWORD))
#define is_function(p) _is_ref_type(k2l_ty_function, p)
#define is_string(p) _is_ref_type(k2l_ty_string, p)
#define is_specialop(p) _is_ref_type(k2l_ty_specialop, p)
#define is_lambdafn(p) _is_ref_type(k2l_ty_lambdafn, p)
#define is_macro(p) _is_ref_type(k2l_ty_macro, p)
#define is_stream(p) _is_ref_type(k2l_ty_stream, p)
#define is_hashtab(p) _is_ref_type(k2l_ty_hashtab, p)
#define is_env(p)  _is_ref_type(k2l_ty_env, p)
#define is_syspriv(p) _is_ref_type(k2l_ty_syspriv, p)
#define is_longjmp(p) (is_syspriv(p) && (SYS_TYPE(p) == K2L_SYS_TYPE_LONGJMP))
#define is_input_stream(p) (is_stream(p) && (STREAM_FLAGS(p) & 1))
#define is_output_stream(p) (is_stream(p) && (STREAM_FLAGS(p) & 2))
#define is_list(p) (is_cons(p) || is_nil(p))

#ifdef _DBG_MEMCNT
# define sexp_ref_up(p) do { if (!is_imm(p) && SOBJ_REF(p) && SOBJ_REF(p)++) { \
			printf("[ref_up  ] ref=%d : ", SOBJ_REF(p)); k2l_disp_line(p, K2L_STDOUT); }} while(0)
# define del_sexp(p) do { if (!is_imm(p) && SOBJ_REF(p)) {	\
			printf("[del_sexp] ref=%d : ", SOBJ_REF(p)); k2l_disp_line(p, K2L_STDOUT); \
			if (--SOBJ_REF(p) == 0 && SOBJ_OPS(p) && SOBJ_OPS(p)->release) \
			{ SOBJ_OPS(p)->release(p); }}} while(0)
#else
# define sexp_ref_up(p) (!is_imm(p) && SOBJ_REF(p) && SOBJ_REF(p)++)
# define del_sexp(p) do { if (!is_imm(p) && SOBJ_REF(p) && --SOBJ_REF(p) == 0 && SOBJ_OPS(p) && SOBJ_OPS(p)->release) { \
			SOBJ_OPS(p)->release(p); }} while(0);
#endif

#define i2ps(v) ((k2l_sexp_ref){(k2l_sobj_t*)((((k2l_int)v)<<2)+SREF_TYPE_INT)})
#define c2ps(v) ((k2l_sexp_ref){(k2l_sobj_t*)((((k2l_char)v)<<2)+SREF_TYPE_CHR)})
#define K2L_NIL   {(k2l_sobj_t*)NULL}
#define K2L_UNDEF {(k2l_sobj_t*)SREF_TYPE_UNDEF}
#define K2L_SYM_QUOTE    (g_k2l_rsv_symtab[0])
#define K2L_SYM_BACKQ    (g_k2l_rsv_symtab[1])
#define K2L_SYM_EQ       (g_k2l_rsv_symtab[2])
#define K2L_SYM_STRINGEQ (g_k2l_rsv_symtab[3])
#define K2L_SYM_LAMBDA   (g_k2l_rsv_symtab[4])
#define K2L_STDIN        (SYMBOL_VALUE(g_k2l_rsv_symtab[5]))
#define K2L_STDOUT       (SYMBOL_VALUE(g_k2l_rsv_symtab[6]))

extern const k2l_sexp_ref g_k2l_nil;
extern const k2l_sexp_ref g_k2l_t;
extern const k2l_sexp_ref g_k2l_undef;
extern const k2l_sexp_ref g_k2l_rsv_symtab[];

extern k2l_sexp_ref new_sexp(k2l_u32 sz, k2l_type_t *type);
extern k2l_sexp_ref new_cons(k2l_sexp_ref a, k2l_sexp_ref d);
extern k2l_sexp_ref new_string(k2l_char* p);
extern k2l_sexp_ref new_symbol(k2l_sexp_ref name, k2l_sexp_ref value, k2l_sexp_ref func);
extern k2l_sexp_ref new_list(k2l_u32 len, ...);
extern k2l_sexp_ref new_hashtab(k2l_sexp_ref testf);
extern k2l_sexp_ref new_env(k2l_sexp_ref parent);
extern k2l_sexp_ref new_lambdafn(k2l_sexp_ref env, k2l_sexp_ref name, k2l_sexp_ref def);
extern k2l_sexp_ref new_filedesc(k2l_int fd);
extern k2l_sexp_ref k2l_eval(k2l_sexp_ref env, k2l_sexp_ref p, k2l_sexp_ref *mv);
extern k2l_sexp_ref k2l_read(k2l_sexp_ref env, k2l_sexp_ref stream);
extern void         k2l_disp(k2l_sexp_ref sexp, k2l_sexp_ref stream);
extern void         k2l_disp_line(k2l_sexp_ref sexp, k2l_sexp_ref stream);
extern void         k2l_load(k2l_char *file);
extern k2l_int      k2l_repl(k2l_int argc, k2l_char **argv);
#endif /*  __K2L_H__ */
